/**
 * Copyright &copy; 2016-2017 <a href="http://git.oschina.net/whoamien/backend_management">Backend Management</a> All rights reserved.
 */
package com.xw.framework.encryption;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import com.xw.util.ConvertUtils;
import com.xw.util.RSAUtils;

/**
 * The Class ParameterDecryptingFilter.
 *
 * @author Xi Wei
 */
public class ParameterDecryptingFilter extends OncePerRequestFilter {

	private static final Logger logger = LoggerFactory
			.getLogger(ParameterDecryptingFilter.class);

	private static final String J_CRYPTION = "jCryption";

	/* (non-Javadoc)
	 * @see org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
	 */
	public void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
			
		HttpServletRequestWrapper wrappedRequest = null;
		String parameter = request.getParameter(J_CRYPTION);
		
		if (parameter != null && parameter.split("|").length > 1) {
			logger.debug("Processing crypted request");						
			
			InputStream is = getClass().getResourceAsStream("/rsa_1024_priv.pem");
			String privateKeyString = ConvertUtils.txt2String(is);
			
			if (privateKeyString != null) {
				wrappedRequest = new DecryptedParameterRequest(request, privateKeyString);
			} else {
				logger.debug("No keys in session");
			}
		}
		filterChain.doFilter(wrappedRequest != null ? wrappedRequest : request,
				response);
	}

	/**
	 * The Class DecryptedParameterRequest.
	 *
	 * @author Xi Wei
	 */
	class DecryptedParameterRequest extends HttpServletRequestWrapper {

		private Map<String, String[]> parameters = new LinkedHashMap<String, String[]>(
				16);

		/**
		 * Instantiates a new DecryptedParameterRequest.
		 *
		 * @param request the request
		 * @param keys the keys
		 */
		public DecryptedParameterRequest(HttpServletRequest request,
				String keys) {
			super(request);

			String parameter = request.getParameter(J_CRYPTION);
			String[] split = parameter.split("\\|");
			
			String aesKeyEncrypted = split[0];
			String encryptedParameter = split[1];
			
			String aesKey = RSAUtils.decodeSecret(keys, aesKeyEncrypted);
			
			//String decrypted = RSAUtils.decodeSecret(keys, request.getParameter(J_CRYPTION));
			String decrypted = AESUtil.decryptForJS(aesKey, encryptedParameter);
			parameters = parse(decrypted, "utf-8");
			if (logger.isDebugEnabled()) {
				for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
					logger.debug(entry.getKey() + ": "
							+ java.util.Arrays.toString(entry.getValue()));
				}
			}
		}
		
		/**
		 * Parse url string (Todo - better parsing algorithm).
		 *
		 * @param url            value to parse
		 * @param encoding            encoding value
		 * @return Map with param name, value pairs
		 */
		public Map<String, String[]> parse(String url, String encoding) {
			try {
				String urlToParse = URLDecoder.decode(url, encoding);
				String[] params = urlToParse.split("&");
				Map<String, String[]> parsed = new HashMap<String, String[]>();
				for (int i = 0; i < params.length; i++) {
					String[] p = params[i].split("=");
					String key = p[0];
					String value = (p.length == 2) ? p[1] : null;
					if (parsed.containsKey(key)) {
						String[] values = parsed.get(key);
						List<String> list = new LinkedList<String>(
								Arrays.asList(values));
						list.add(value);
						parsed.put(key, list.toArray(new String[list.size()]));
					} else {
						String[] values = {value};
						parsed.put(key, values);
					}
				}
				return parsed;
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException("Unknown encoding.", e);
			}
		}


		/* (non-Javadoc)
		 * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
		 */
		@Override
		public String getParameter(String name) {
			Assert.notNull(name, "Parameter name must not be null");
			String[] arr = this.parameters.get(name);
			return (arr != null && arr.length > 0 ? arr[0] : null);
		}

		/* (non-Javadoc)
		 * @see javax.servlet.ServletRequestWrapper#getParameterMap()
		 */
		@Override
		public Map getParameterMap() {
			return Collections.unmodifiableMap(this.parameters);
		}

		/* (non-Javadoc)
		 * @see javax.servlet.ServletRequestWrapper#getParameterNames()
		 */
		@Override
		public Enumeration getParameterNames() {
			return Collections.enumeration(this.parameters.keySet());
		}

		/* (non-Javadoc)
		 * @see javax.servlet.ServletRequestWrapper#getParameterValues(java.lang.String)
		 */
		@Override
		public String[] getParameterValues(String name) {
			Assert.notNull(name, "Parameter name must not be null");
			return this.parameters.get(name);
		}
	}

}
